{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# DefinedAEpTandZ0 media example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from matplotlib.ticker import AutoMinorLocator\n",
    "from numpy import log, sum\n",
    "from scipy.optimize import minimize\n",
    "\n",
    "import skrf as rf\n",
    "import skrf.mathFunctions as mf\n",
    "\n",
    "rf.stylely()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Measurement of two CPWG lines with different lengths\n",
    "\n",
    "The measurement where performed the 21th March 2017 on a Anritsu MS46524B 20GHz Vector Network Analyser. The setup is a linear frequency sweep from 1MHz to 10GHz with 10'000 points. Output power is 0dBm, IF bandwidth is 1kHz and neither averaging nor smoothing are used.\n",
    "\n",
    "CPWGxxx is a L long, W wide, with a G wide gap to top ground, T thick copper coplanar waveguide on ground on a H height substrate with top and bottom ground plane. A closely spaced via wall is placed on both side of the line and the top and bottom ground planes are connected by many vias.\n",
    "\n",
    "| Name | L (mm) | W (mm) | G (mm) | H (mm) | T (um) | Substrate |\n",
    "| :--- | ---: | ---: | ---: | ---: | ---: | :--- |\n",
    "| MSL100 | 100 | 1.70 | 0.50 | 1.55 | 50 | FR-4 |\n",
    "| MSL200 | 200 | 1.70 | 0.50 | 1.55 | 50 | FR-4 |\n",
    "\n",
    "The milling of the artwork is performed mechanically with a lateral wall of 45°.\n",
    "\n",
    "The relative permittivity of the dielectric was assumed to be approximately 4.5 for design purpose.\n",
    "\n",
    "![MSL100 and MSL200 illustration, both are microstripline, MSL200 is twice the length of MSL100](MSL_CPWG_100_200.jpg \"MSL100 and MSL200\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load raw measurements\n",
    "TL100 = rf.Network('CPWG100.s2p')\n",
    "TL200 = rf.Network('CPWG200.s2p')\n",
    "TL100_dc = TL100.extrapolate_to_dc(kind='linear')\n",
    "TL200_dc = TL200.extrapolate_to_dc(kind='linear')\n",
    "\n",
    "plt.figure()\n",
    "plt.suptitle('Raw measurement')\n",
    "TL100.plot_s_db()\n",
    "TL200.plot_s_db()\n",
    "\n",
    "plt.figure()\n",
    "t0 = -2\n",
    "t1 = 4\n",
    "plt.suptitle('Time domain reflexion step response (DC extrapolation)')\n",
    "ax = plt.subplot(1, 1, 1)\n",
    "TL100_dc.plot_z_time_step(0, 0, ax=ax, color='0.0')\n",
    "TL200_dc.plot_z_time_step(0, 0, ax=ax, color='0.2')\n",
    "ax.set_xlim(t0, t1)\n",
    "ax.xaxis.set_minor_locator(AutoMinorLocator(10))\n",
    "ax.yaxis.set_minor_locator(AutoMinorLocator(5))\n",
    "ax.patch.set_facecolor('1.0')\n",
    "ax.grid(True, color='0.8', which='minor')\n",
    "ax.grid(True, color='0.4', which='major')\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Impedance from the line and from the connector section may be estimated on the step response.\n",
    "The line section is not flat, there is some variation in the impedance which may be induced by manufacturing tolerances and dielectric inhomogeneity.\n",
    "\n",
    "Note that the delay on the reflexion plot are twice the effective section delays because the wave travel back and forth on the line.\n",
    "\n",
    "Connector discontinuity is about 50 ps long. TL100 line plateau (flat impedance part) is about 450 ps long."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Z_conn = 53.2    # ohm, connector impedance\n",
    "Z_line = 51.4    # ohm, line plateau impedance\n",
    "d_conn = 0.05e-9 # s,   connector discontinuity delay\n",
    "d_line = 0.45e-9 # s,   line plateau delay, without connectors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dielectric effective relative permittivity extraction by multiline method"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Make the missing reflect measurement\n",
    "#This is possible because we already have existing calibration\n",
    "#and know what the open measurement would look like at the reference plane\n",
    "#'refl_offset' needs to be set to -half_thru - connector_length.\n",
    "reflect = TL100.copy()\n",
    "reflect.s[:,0,0] = 1\n",
    "reflect.s[:,1,1] = 1\n",
    "reflect.s[:,1,0] = 0\n",
    "reflect.s[:,0,1] = 0\n",
    "\n",
    "# Perform NISTMultilineTRL algorithm. Reference plane is at the center of the thru.\n",
    "cal = rf.NISTMultilineTRL([TL100, reflect, TL200], [1], [0, 100e-3], er_est=3.0, refl_offset=[-56e-3])\n",
    "\n",
    "plt.figure()\n",
    "plt.title('Corrected lines')\n",
    "cal.apply_cal(TL100).plot_s_db()\n",
    "cal.apply_cal(TL200).plot_s_db()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Calibration results shows a very low residual noise floor. The error model is well fitted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from skrf.media import DefinedAEpTandZ0\n",
    "\n",
    "freq  = TL100.frequency\n",
    "f     = TL100.frequency.f\n",
    "f_ghz = TL100.frequency.f/1e9\n",
    "L     = 0.1\n",
    "A     = 0.0\n",
    "f_A   = 1e9\n",
    "ep_r0 = 2.0\n",
    "tanD0 = 0.001\n",
    "f_ep  = 1e9\n",
    "x0 = [ep_r0, tanD0]\n",
    "\n",
    "ep_r_mea = cal.er_eff.real\n",
    "A_mea    = 20/log(10)*cal.gamma.real\n",
    "\n",
    "def model(x, freq, ep_r_mea, A_mea, f_ep):\n",
    "    ep_r, tanD = x[0], x[1]\n",
    "    m = DefinedAEpTandZ0(frequency=freq, ep_r=ep_r, tanD=tanD, z0=50,\n",
    "                              f_low=1e3, f_high=1e18, f_ep=f_ep, model='djordjevicsvensson')\n",
    "    ep_r_mod = m.ep_r_f.real\n",
    "    A_mod = m.alpha * log(10)/20\n",
    "    return sum((ep_r_mod - ep_r_mea)**2)  + 0.001*sum((20/log(10)*A_mod - A_mea)**2)\n",
    "\n",
    "res = minimize(model, x0, args=(TL100.frequency, ep_r_mea, A_mea, f_ep),\n",
    "               bounds=[(2, 4), (0.001, 0.013)])\n",
    "ep_r, tanD = res.x[0], res.x[1]\n",
    "\n",
    "print(f'epr={ep_r:.3f}, tand={tanD:.4f} at {f_ep * 1e-9:.1f} GHz.')\n",
    "\n",
    "m = DefinedAEpTandZ0(frequency=freq, ep_r=ep_r, tanD=tanD, z0=50,\n",
    "                              f_low=1e3, f_high=1e18, f_ep=f_ep, model='djordjevicsvensson')\n",
    "\n",
    "plt.figure()\n",
    "plt.suptitle('Effective relative permittivity and attenuation')\n",
    "plt.subplot(2,1,1)\n",
    "plt.ylabel(r'$\\epsilon_{r,eff}$')\n",
    "plt.plot(f_ghz, ep_r_mea, label='measured')\n",
    "plt.plot(f_ghz, m.ep_r_f.real, label='model')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(2,1,2)\n",
    "plt.xlabel('Frequency [GHz]')\n",
    "plt.ylabel('A (dB/m)')\n",
    "plt.plot(f_ghz, A_mea, label='measured')\n",
    "plt.plot(f_ghz, 20/log(10)*m.alpha, label='model')\n",
    "plt.legend()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Relative permittivity $\\epsilon_{e,eff}$ and attenuation $A$ shows a reasonable agreement.\n",
    "\n",
    "A better agreement could be achieved by implementing the Kirschning and Jansen microstripline dispersion model or using a linear correction."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Connectors effects estimation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# note: a half line is embedded in connector network\n",
    "coefs = cal.coefs\n",
    "r = mf.sqrt_phase_unwrap(coefs['forward reflection tracking'])\n",
    "s1 = np.array([[coefs['forward directivity'],r],\n",
    "        [r, coefs['forward source match']]]).transpose()\n",
    "\n",
    "conn = TL100.copy()\n",
    "conn.name = 'Connector'\n",
    "conn.s = s1\n",
    "\n",
    "# delay estimation,\n",
    "phi_conn = (np.angle(conn.s[:500,1,0]))\n",
    "z = np.polyfit(f[:500], phi_conn, 1)\n",
    "p = np.poly1d(z)\n",
    "delay = -z[0]/(2*np.pi)\n",
    "print(f'Connector + half thru delay: {delay * 1e12:.0f} ps')\n",
    "print(f'TDR readed half thru delay: {d_line/2 * 1e12:.0f} ps')\n",
    "d_conn_p = delay - d_line/2\n",
    "print(f'Connector delay: {d_conn_p * 1e12:.0f} ps')\n",
    "\n",
    "\n",
    "# connector model with guessed loss\n",
    "half = m.line(d_line/2, 's', z0=Z_line)\n",
    "mc = DefinedAEpTandZ0(m.frequency, ep_r=1, tanD=0.025, z0=50,\n",
    "                              f_low=1e3, f_high=1e18, f_ep=f_ep, model='djordjevicsvensson')\n",
    "left = mc.line(d_conn_p, 's', z0=Z_conn)\n",
    "right = left.flipped()\n",
    "check = mc.thru() ** left ** half ** mc.thru()\n",
    "\n",
    "plt.figure()\n",
    "plt.suptitle('Connector + half thru comparison')\n",
    "plt.subplot(2,1,1)\n",
    "conn.plot_s_deg(1, 0, label='measured')\n",
    "check.plot_s_deg(1, 0, label='model')\n",
    "plt.ylabel('phase (rad)')\n",
    "plt.legend()\n",
    "\n",
    "plt.subplot(2,1,2)\n",
    "conn.plot_s_db(1, 0, label='Measured')\n",
    "check.plot_s_db(1, 0, label='Model')\n",
    "plt.xlabel('Frequency (GHz)')\n",
    "plt.ylabel('Insertion Loss (dB)')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Connector + thru plots shows a reasonable agreement between calibration results and model."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Final check"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "DUT = m.line(d_line, 's', Z_line)\n",
    "DUT.name = 'model'\n",
    "\n",
    "Check = m.thru() ** left ** DUT ** right ** m.thru()\n",
    "Check.name = 'model with connectors'\n",
    "\n",
    "plt.figure()\n",
    "TL100.plot_s_db()\n",
    "Check.plot_s_db(1,0, color='k')\n",
    "Check.plot_s_db(0,0, color='k')\n",
    "plt.show()\n",
    "\n",
    "Check_dc = Check.extrapolate_to_dc(kind='linear')\n",
    "\n",
    "plt.figure()\n",
    "plt.suptitle('Time domain step-response')\n",
    "ax = plt.subplot(1,1,1)\n",
    "TL100_dc.plot_z_time_step(0, 0, ax=ax, color='k')\n",
    "Check_dc.plot_z_time_step(0, 0, ax=ax, color='b')\n",
    "t0 = -2\n",
    "t1 = 4\n",
    "ax.set_xlim(t0, t1)\n",
    "ax.xaxis.set_minor_locator(AutoMinorLocator(10))\n",
    "ax.yaxis.set_minor_locator(AutoMinorLocator(5))\n",
    "ax.patch.set_facecolor('1.0')\n",
    "ax.grid(True, color='0.8', which='minor')\n",
    "ax.grid(True, color='0.5', which='major')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The plots shows a reasonable agreement between model and measurement up to 4 GHz.\n",
    "\n",
    "Further works may include implementing CPWG medium or modeling the line by more sections to account the impedance variation vs. position."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.16"
  },
  "vscode": {
   "interpreter": {
    "hash": "637c323cf467337602e9974a89cb4d3fc95fac3ef875a73e62754f8e768d8de7"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
